#include <ProcessThread/ProcessThread.h>
#include <IntExcept/IntExcept.h>
#include <Kernel/GlobalData.h>
#include <Utility/CLib.h>
#include <Utility/intrinsic.h>

/*
@brief 调度线程
@param `lpIntExceptContext` 中断上下文
@note 此函数可由中断处理函数调用，此时 `lpIntExceptContext` 非 `NULL`，保存当前被中断执行流的中断现场，将其加入就绪队列后再调度。
@note 若 `lpIntExceptContext` 为 `NULL`，则不会保存当前执行流的中断现场，直接进行调度。
*/

void ScheduleThread(IntExceptContext* lpIntExceptContext)
{
    if (!lpIntExceptContext)
    {
        goto dispatch_another_thread;
    }
save_current_thread:
    LPThreadControlBlock lpCurrentThread = GetCurrentThread();
    /* 需要挂起的线程应加入相应的挂起队列 */
    if (lpCurrentThread->dwStatus == THREAD_SUSPENDED)
    {

    }
    /* 时间片到 */
    else if (--lpCurrentThread->nTicks == 0)
    {
        AppendTCBToQueue(g_lpReadyTCBQueue, lpCurrentThread); /* 将正在运行的线程加入就绪队列 */
        lpCurrentThread->dwStatus = THREAD_READY; /* 修改线程状态为就绪 */
    }
    else /* 不需挂起，时间片也未到 */
    {
        return; /* 不进行调度 */
    }
    memcpy(&lpCurrentThread->context, lpIntExceptContext, sizeof(IntExceptContext)); /* 保存当前线程上下文到线程 A 的 TCB 中 */
dispatch_another_thread:
    LPThreadControlBlock lpDispatch = FetchTCBFromQueue(g_lpReadyTCBQueue); /* 从就绪队列中取出下个等待执行的线程 thread B */
    KERNEL_ASSERT(lpDispatch, "lpDispatch is NULL\r\n.");
    /* 恢复线程 B 的运行环境 */
    LPIntExceptContext lpContext;
    if ((lpDispatch->context.dwCS & 0x11) == 0) /* RPL 是否为 0 */
    {
        lpDispatch->context.dwESP -= sizeof(IntExceptContext); /* B 内核栈开辟一块空间存放 context */
        lpContext = (LPVOID)lpDispatch->context.dwESP;
    }
    else
    {
        lpContext = (LPVOID)((DWORD)lpDispatch->lpKernelStack - sizeof(IntExceptContext));
    }
    memcpy(lpContext, &lpDispatch->context, sizeof(IntExceptContext)); /* 在构造的中断栈中填充 thread B 线程上下文 */
    lpDispatch->nTicks = lpDispatch->dwPriority; /* 优先级即线程可运行的时间片 */
    lpDispatch->dwStatus = THREAD_RUNNING;
    /* 恢复 thread B 的页目录 */
    if (READ_CR3_32() != lpDispatch->lpProcess->dwCR3) /* 需要进行页表切换，页表切换清空 TLB，效率起见先进行判断 */
    {
        WRITE_CR3_32(lpDispatch->lpProcess->dwCR3); /* 由于内核空间与主控页表同步，因此切换页表不影响对当前栈帧和代码段的访问 */
        // ksuccess("CR3 = 0x%x\r\n", READ_CR3_32());
    }
    WORD wTRIndex = STORE_TR_32() >> 3; /* 获取 TSS 段描述符在 GDT 中的位置 */
    QWORD qwGDTR;
    STORE_GDT_32(&qwGDTR);
    qwGDTR >>= 16; /* GDT 基址 */
    GlobalDescriptor* lpGDT = (LPVOID)qwGDTR;
    LPTaskStateSegment lpTSS = (LPTaskStateSegment)RESOLVE_BASE_FROM_DESCRIPTOR(*(lpGDT + 5)); /* 取出 TSS 地址 */
    lpTSS->dwRing0ESP = (DWORD)lpDispatch->lpKernelStack; /* 修改为 B 的栈顶 */
    RestoreThreadContext((DWORD)lpContext); /* 切换到 B 线程的内核栈帧，然后中断返回实现线程切换 */
}